Skip to content

feat(sync): allow nested local roots via a managed primary-folder ignore block#157

Merged
acmore merged 2 commits into
mainfrom
feat/sync-nested-local-roots
Jul 5, 2026
Merged

feat(sync): allow nested local roots via a managed primary-folder ignore block#157
acmore merged 2 commits into
mainfrom
feat/sync-nested-local-roots

Conversation

@acmore

@acmore acmore commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Follow-up to #156: the disjoint-only rule forced results directories to live outside the repo (../collected), which is awkward. An additional mapping's local root may now nest inside the primary mapping's local root — ./results inside the repo just works. Remote roots stay disjoint-only.

How the exclusion is guaranteed (not raced)

The primary folder must not carry the nested subtree, or the same files would have two opposing authorities. okdev maintains a managed block in the primary root's .stignore:

// okdev:begin managed sync excludes
/collected
// okdev:end managed sync excludes

(Syncthing's comment syntax is //#... lines would be patterns.) Ordering is structural: adding a mapping changes the sync config hash → the sync process chain restarts → the block is merged into the file before the local syncthing daemon starts, so the daemon's first scan already honors it. If a hot-apply path is ever added, it must go through PUT /rest/db/ignores (atomic), never raw file edits.

Removal semantics: never silently widen a folder

Removing a nested mapping prunes its syncthing folder (both sides) and keeps local files, but the ignore entry is retained as a tombstone:

// okdev: retained after mapping removal - delete the next line to sync it via the primary folder
/collected

Without this, removal would bulk-sync the subtree through the code channel — a surprise transfer with data-overwrite potential. Re-inclusion is an explicit one-line deletion. Sync start prints a notice for newly retained entries; status --details lists active and retained excludes.

Validation matrix

  • allowed: extra local nested inside the primary local root
  • rejected: equal local roots (impossible in syncthing — shared .stignore), nesting between additional mappings, an extra containing the primary, any remote overlap

Testing

  • Unit: validation matrix, ignore-pattern derivation (/rel/path, sorted), managed-block merge (create / idempotent / tombstone / re-add / user content preserved before & after block), no-op without file+patterns, status rendering.
  • Kind e2e: nested ./collected mapping — exclusion effective from first scan (/workspace/collected never appears on the pod while results flow through the child folder), managed block asserted in .stignore, status shows the exclude; then the mapping is removed and the tombstone + still-excluded behavior is asserted end-to-end.
  • gofmt -l clean; go test -race ./... passes.

🤖 Generated with Claude Code

acmore and others added 2 commits July 5, 2026 21:10
…ore block

An additional mapping's local root may now nest inside the primary
mapping's local root (./results inside the repo) — the natural place
for a results directory — instead of forcing an awkward sibling
placement. Remote roots stay disjoint-only.

okdev maintains a managed block in the primary root's .stignore
("// okdev:begin/end managed sync excludes"; syncthing comments are
"//", not "#") holding one /rel-path pattern per nested child, so the
subtree travels only through its own folder. Ordering is a hard
guarantee, not a race: adding a mapping changes the sync config hash,
which restarts the sync process chain, and the block is merged into the
file BEFORE the local syncthing daemon starts — its first scan already
honors the exclusion.

Removing a mapping never silently widens the primary folder: the child
folder is pruned from both syncthing instances and local files are
kept, but the ignore entry is retained as a tombstone with an
explanatory comment; re-inclusion is the user's explicit one-line
deletion. Sync start prints a notice for newly retained entries, and
status --details lists active and retained excludes.

Other local overlaps (equal roots, nesting between additional mappings,
an extra containing the primary) remain rejected. Kind e2e now runs the
nested layout end-to-end: exclusion effective from first scan (no leak
into /workspace), results flow through the child folder, and removal
leaves a tombstone with the subtree still excluded.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ted mappings

- Skill: SKILL.md gets a sync-capabilities line; workflows.md gains the
  result-collection-via-sync recipe; troubleshooting.md explains the two
  by-design "not syncing" cases (direction contract, managed excludes /
  tombstones) before fault-hunting.
- docs/troubleshooting.md: drop the stale single-mapping claim, add the
  same by-design guidance, and qualify the versioning section (directional
  mappings are structurally immune to cross-side overwrites).
- command-reference: nested-local + tombstone semantics under `okdev
  sync`; the already-active no-op wording follows the new default-mode
  semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@acmore acmore merged commit 91986d6 into main Jul 5, 2026
1 check passed
@acmore acmore deleted the feat/sync-nested-local-roots branch July 5, 2026 13:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant